﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;

namespace PonGun
{
    public class CollisionManager
    {
        public Game1 Game;

        public List<Base2DObject> ObjectList = new List<Base2DObject>();
        public List<Base2DObject> UnRegisterObjectList = new List<Base2DObject>();
        public List<Base2DObject> RegisterObjectList = new List<Base2DObject>();

        protected Dictionary<Vector2, Rectangle> theGrid = new Dictionary<Vector2, Rectangle>();
        protected Dictionary<Base2DObject, List<Vector2>> ObjZones = new Dictionary<Base2DObject, List<Vector2>>();
        protected Dictionary<Vector2, List<Base2DObject>> ZoneObjecs = new Dictionary<Vector2, List<Base2DObject>>();

        public bool Debug { get; set; }

        public CollisionManager(Game1 game)
        {
            Game = game;

            Game.Services.AddService(this.GetType(), this);

            SetGrid();            
        }

        public void ReSet()
        {
            ObjectList.Clear();
            UnRegisterObjectList.Clear();
            RegisterObjectList.Clear();

            ObjZones.Clear();
            ZoneObjecs.Clear();
            theGrid.Clear();

            SetGrid();            
        }

        public void SetGrid()
        {
            // Grid is a n x n grid..
            int dim = 20;
            Vector2 ids = Vector2.Zero;
            Vector2 recSize = new Vector2(Game1.RenderArea.Width / dim, Game1.RenderArea.Height / dim);

            for (int x = 0; x < dim; x++)
            {
                for (int y = 0; y < dim; y++)
                {
                    ids = new Vector2(x, y) * recSize;
                    theGrid.Add(ids, new Rectangle((int)ids.X, (int)ids.Y, (int)recSize.X, (int)recSize.Y));
                    ZoneObjecs.Add(ids, new List<Base2DObject>());
                }
            }
        }

        public void UpdateObject(Base2DObject obj)
        {
            UnRegister(obj, !inUpdate);
            RegisterObject(obj, !inUpdate);
        }

        public void UnRegister(Base2DObject obj, bool imediate = true)
        {
            if (imediate)
            {  
                if (ObjectList.Contains(obj))
                {
                    obj.Registered = false;
                    ObjectList.Remove(obj);

                    foreach (Vector2 zone in ObjZones[obj])
                        ZoneObjecs[zone].Remove(obj);

                    ObjZones.Remove(obj);
                }
            }
            else
                UnRegisterObjectList.Add(obj);
        }

        public void RegisterObject(Base2DObject obj, bool imediate = true)
        {

            if (obj.RegisteredCollider && !obj.Registered)
            {
                if (imediate)
                {
                    obj.Registered = true;
                    ObjectList.Add(obj);

                    // Get top Key.
                    Dictionary<Vector2, Rectangle> zones = theGrid.Where(entity => entity.Value.Intersects(obj.Bounds)).ToDictionary(entity => entity.Key, entity => entity.Value);
                    if (!ObjZones.ContainsKey(obj))
                        ObjZones.Add(obj, new List<Vector2>());

                    ObjZones[obj] = zones.Keys.ToList();

                    foreach (Vector2 key in zones.Keys)
                    {
                        if (!ZoneObjecs[key].Contains(obj))
                            ZoneObjecs[key].Add(obj);
                    }
                }
                else
                    RegisterObjectList.Add(obj);
            }
        }

        bool inUpdate = false;
        public void Update(GameTime gameTime)
        {
            inUpdate = true;
            Debug = Game1.Debug;
            List<CollisionData> colChecked = new List<CollisionData>();
            CollisionData cob = null;

            foreach (Base2DObject obj in ObjectList)
            {   
                // get the objects in the same blocks as me...
                List<Vector2> thisZones = ObjZones[obj];
                foreach (Vector2 zone in thisZones)
                {
                    foreach (Base2DObject ob in ZoneObjecs[zone])
                    {
                        cob = new CollisionData(obj, ob);
                        if (ob != obj && !colChecked.Contains(cob))
                        {
                            if (ob.Bounds.Intersects(obj.Bounds))
                            {
                                // Do a point of contact test for drilling into an object..
                                Vector2 vp = obj.GetContactPosition(ob);
                                
                                //Vector2 vp2 = ob.GetContactPosition(obj);
                                if (vp != -Vector2.One)// && vp2 != Vector2.Zero)
                                {
                                    // Undo intersection.                                    
                                    //obj.Collided(cob);
                                    ob.Collided(cob);
                                    colChecked.Add(cob);
                                    colChecked.Add(new CollisionData(ob, obj));
                                }                                
                            }                            
                        }
                    }
                }
            }

            inUpdate = false;

            foreach (Base2DObject o in UnRegisterObjectList)
                UnRegister(o);

            UnRegisterObjectList.Clear();

            foreach (Base2DObject o in RegisterObjectList)
                RegisterObject(o);

            RegisterObjectList.Clear();

            
        }

        public void UndoIntersection(Base2DObject o1, Base2DObject o2)
        {
            // Who is moving.
            if (o1.Velocity != Vector2.Zero && o2.Velocity != Vector2.Zero)
            {
                while (o1.Bounds.Intersects(o2.Bounds))
                {
                    o1.Position -= o1.Velocity;
                    o2.Position -= o2.Velocity;
                }
            }
            else
            {
                // If neither 
                if (o1.Velocity == Vector2.Zero && o2.Velocity == Vector2.Zero)
                {
                    Vector2 dir = Vector2.Normalize(o1.Position - o2.Position);

                    while (o1.Bounds.Intersects(o2.Bounds))
                    {
                        o1.Position -= dir;
                        o2.Position -= -dir;
                    }
                }
                else
                {
                    // Who is then
                    if (o1.Velocity != Vector2.Zero)
                    {
                        while (o1.Bounds.Intersects(o2.Bounds))
                        {
                            o1.Position -= o1.Velocity;
                        }
                    }
                    else
                    {
                        while (o1.Bounds.Intersects(o2.Bounds))
                        {
                            o2.Position -= o2.Velocity;
                        }
                    }
                }
            }
        }

        public void Draw(GameTime gameTime)
        {
            if (Debug)
            {
                foreach (Vector2 key in theGrid.Keys)
                {
                    // Do a box per rectangle..
                    Game.DrawRectangle(theGrid[key], Color.Red);
                }

                foreach (Base2DObject obj in ObjectList)
                    Game.DrawRectangle(obj.Bounds, Color.Blue);
            }
        }
    }
}
